/*
 * AbstractButton.java  0.4.0 / Oct 25, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j.swing;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import com.cute4j.swing.events.ActionEvent;
import com.cute4j.swing.events.ActionListener;
import com.cute4j.swing.events.ChangeEvent;
import com.cute4j.swing.events.ChangeListener;
import com.cute4j.swing.events.SwingEvent;
import com.cute4j.swing.properties.AbstractButtonProperties;

/**
 * Abstract Button Component.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public abstract class AbstractButton extends Component
                                     implements AbstractButtonProperties {

// Static Initialization. ------------------------------------------------------

  static {
    _initialize();
  }

// Private Fields. -------------------------------------------------------------

  private String text;
  private int mnemonic;

  private Action action;
  private String actionCommand;
  private PropertyChangeListener actionPropertyChangeListener; // NOPMD
  private ChangeEvent changeEvent;
  private final ChangeListener changeListener;
  
// Package-Private Fields. -----------------------------------------------------

  SwingIconSet iconSet;
  ButtonModel model;
  boolean blockModelEvents;
  boolean pressed;

// Protected Constructors. -----------------------------------------------------

  /**
   * Constructs new AbstractButton object.
   */
  protected AbstractButton() {
    changeListener = new ModelListener(this);
    model = new DefaultButtonModel();
    model.addChangeListener(changeListener);
  }

  /**
   * Constructs new AbstractButton object.
   *
   * @param action the action
   */
  protected AbstractButton(Action action) {
    this();
    setAction(action); // NOPMD
  }

  /**
   * Constructs new AbstractButton object.
   *
   * @param text the button's text
   */
  protected AbstractButton(String text) {
    this(text, null);
  }

  /**
   * Constructs new AbstractButton object.
   *
   * @param icon the icon
   */
  protected AbstractButton(Icon icon) {
    this(null, icon);
  }

  /**
   * Constructs new AbstractButton object.
   *
   * @param text the button's text
   * @param icon the icon
   */
  protected AbstractButton(String text, Icon icon) {
    this();
    if (text != null) {
      this.text = text;
      _setText(_delegate, text); // NOPMD
    }
    if (icon != null) {
      iconSet = new SwingIconSet(this);
      _setIcon(_delegate, iconSet); // NOPMD
      iconSet.setIcon(icon, null);
      _setIconSize(_delegate, icon.getIconWidth(), icon.getIconHeight()); // NOPMD
    }
  }

// Public Methods. -------------------------------------------------------------

  /**
   * Returns button model.
   *
   * @return button model
   */
  public ButtonModel getModel() {
    return model;
  }

  /**
   * Sets button model.
   *
   * @param model the button model
   */
  public void setModel(ButtonModel model) { // NOPMD
    final ButtonModel oldModel = this.model;
    if (oldModel != model) { // NOPMD
      if (model == null) {
        if (oldModel == DefaultButtonModel.EmptyModel.INSTANCE) { // NOPMD
          return;
        }
        model = DefaultButtonModel.EmptyModel.INSTANCE; // NOPMD
      }
      disposedCheck();
      if (oldModel != null) {
        oldModel.removeChangeListener(changeListener);
      }
      this.model = model;
      processChangeEvent();
      model.addChangeListener(changeListener);
      firePropertyChange(PROPERTY_MODEL, oldModel, model);
    }
  }

  /**
   * Returns <tt>true</tt> if the button is pressed.
   *
   * @return <tt>true</tt> if the button is pressed
   */
  public boolean isPressed() {
    return pressed;
  }

  /**
   * Pressed/unpressed the button.
   *
   * @param pressed <tt>true</tt> if pressed
   */
  public void setPressed(boolean pressed) {
    model.setPressed(pressed);
  }

  /**
   * Returns button's text.
   *
   * @return button's text
   */
  public String getText() {
    return text;
  }

  /**
   * Sets button's text.
   *
   * @param text the button's text
   */
  public void setText(String text) {
    final String oldText = this.text;
    if (oldText == null) {
      if (text == null) {
        return;
      }
    } else {
      if (oldText.equals(text)) {
        return;
      }
    }
    disposedCheck();
    _setText(_delegate, text);
    mnemonic = SwingUtilities.getMnemonicIndex(text);
    this.text = text;
    invalidate();
    firePropertyChange(PROPERTY_TEXT, oldText, text);
  }

  /**
   * Returns the keyboard mnemonic.
   *
   * @return the keyboard mnemonic
   */
  public int getMnemonic() {
    return mnemonic;
  }

  /**
   * Sets the keyboard mnemonic.
   *
   * @param mnemonic the keyboard mnemonic
   */
  public void setMnemonic(int mnemonic) {
    final int oldMnemonic = this.mnemonic;
    if (oldMnemonic != mnemonic) {
      if (mnemonic == -1) {
        if (text != null) {
          _setText(_delegate, text);
          this.mnemonic = mnemonic;
          firePropertyChange(PROPERTY_MNEMONIC, oldMnemonic, mnemonic);
        }
      } else {
        if ((text != null) && (text.length() > mnemonic)) {
          StringBuffer sb = new StringBuffer(text);
          sb.insert(mnemonic, '&');
          _setText(_delegate, sb.toString());
          this.mnemonic = mnemonic;
          firePropertyChange(PROPERTY_MNEMONIC, oldMnemonic, mnemonic);
        }
      }
    }
  }

  /**
   * Returns icon.
   *
   * @return icon
   */
  public Icon getIcon() {
    if (iconSet != null) {
      return iconSet.getIcon();
    }
    return null;
  }

  /**
   * Sets icon.
   *
   * @param icon the icon
   */
  public void setIcon(Icon icon) {
    disposedCheck();
    if (icon == null) {
      if (iconSet != null) {
        iconSet.setIcon(null, PROPERTY_ICON);
        if (iconSet.isEmpty()) {
          _setIcon(_delegate, null);
          iconSet = null;
        }
      }
    } else {
      if (iconSet == null) {
        iconSet = new SwingIconSet(this);
        _setIcon(_delegate, iconSet);
      }
      _setIconSize(_delegate, icon.getIconWidth(), icon.getIconHeight());
      iconSet.setIcon(icon, PROPERTY_ICON);
      invalidate();
    }
  }

  /**
   * Returns disabled icon.
   *
   * @return disabled icon
   */
  public Icon getDisabledIcon() {
    if (iconSet != null) {
      return iconSet.getDisabledIcon();
    }
    return null;
  }

  /**
   * Sets disabled icon.
   *
   * @param icon the disabled icon
   */
  public void setDisabledIcon(Icon icon) {
    disposedCheck();
    if (icon == null) {
      if (iconSet != null) {
        iconSet.setDisabledIcon(null, PROPERTY_DISABLED_ICON);
        if (iconSet.isEmpty()) {
          _setIcon(_delegate, null);
          iconSet = null;
        }
      }
    } else {
      if (iconSet == null) {
        iconSet = new SwingIconSet(this);
        _setIcon(_delegate, iconSet);
      }
      _setIconSize(_delegate, icon.getIconWidth(), icon.getIconHeight());
      iconSet.setDisabledIcon(icon, PROPERTY_DISABLED_ICON);
    }
  }

  /**
   * Returns rollover icon.
   *
   * @return rollover icon
   */
  public Icon getRolloverIcon() {
    if (iconSet != null) {
      return iconSet.getRolloverIcon();
    }
    return null;
  }

  /**
   * Sets rollover icon.
   *
   * @param icon the rollover icon
   */
  public void setRolloverIcon(Icon icon) {
    disposedCheck();
    if (icon == null) {
      if (iconSet != null) {
        iconSet.setRolloverIcon(null, PROPERTY_ROLLOVER_ICON);
        if (iconSet.isEmpty()) {
          _setIcon(_delegate, null);
          iconSet = null;
        }
      }
    } else {
      if (iconSet == null) {
        iconSet = new SwingIconSet(this);
        _setIcon(_delegate, iconSet);
      }
      _setIconSize(_delegate, icon.getIconWidth(), icon.getIconHeight());
      iconSet.setRolloverIcon(icon, PROPERTY_ROLLOVER_ICON);
    }
  }

  /**
   * Returns pressed icon.
   *
   * @return pressed icon
   */
  public Icon getPressedIcon() {
    if (iconSet != null) {
      return iconSet.getPressedIcon();
    }
    return null;
  }

  /**
   * Sets pressed icon.
   *
   * @param icon the pressed icon
   */
  public void setPressedIcon(Icon icon) {
    disposedCheck();
    if (icon == null) {
      if (iconSet != null) {
        iconSet.setPressedIcon(null, PROPERTY_PRESSED_ICON);
        if (iconSet.isEmpty()) {
          _setIcon(_delegate, null);
          iconSet = null;
        }
      }
    } else {
      if (iconSet == null) {
        iconSet = new SwingIconSet(this);
        _setIcon(_delegate, iconSet);
      }
      _setIconSize(_delegate, icon.getIconWidth(), icon.getIconHeight());
      iconSet.setPressedIcon(icon, PROPERTY_PRESSED_ICON);
    }
  }

  /**
   * Returns action.
   *
   * @return action
   */
  public Action getAction() {
    return action;
  }

  /**
   * Sets the action.
   *
   * @param action the action
   */
  public void setAction(Action action) {
    final Action oldAction = this.action;
    if (oldAction == null) {
      if (action == null) {
        return;
      }
    } else {
      if (oldAction.equals(action)) {
        return;
      }
    }
    disposedCheck();
    if (oldAction != null) {
      removeActionListener(oldAction);
      oldAction.removePropertyChangeListener(actionPropertyChangeListener);
      actionPropertyChangeListener = null;
    }
    configurePropertiesFromAction(action);
    if (action != null) {
      if (!listenerList.containsListener(ActionListener.class, action)) {
        addActionListener(action);
      }
      actionPropertyChangeListener = createActionPropertyChangeListener();
      action.addPropertyChangeListener(actionPropertyChangeListener);
    }
    this.action = action;
    firePropertyChange(PROPERTY_ACTION, oldAction, action);
  }

  /**
   * Returns action command.
   *
   * @return action command
   */
  public String getActionCommand() {
    if (actionCommand == null) {
      return text;
    }
    return actionCommand;
  }

  /**
   * Sets action command for this button.
   *
   * @param actionCommand the action command
   */
  public void setActionCommand(String actionCommand) {
    final String oldActionCommand = this.actionCommand;
    if (oldActionCommand == null) {
      if (actionCommand == null) {
        return;
      }
    } else {
      if (oldActionCommand.equals(actionCommand)) {
        return;
      }
    }
    this.actionCommand = actionCommand;
    firePropertyChange(PROPERTY_ACTION_COMMAND, oldActionCommand,
                       actionCommand);
  }

  /**
   * Adds action listener.
   *
   * @param listener the action listener
   */
  public void addActionListener(ActionListener listener) {
    if (listener != null) {
      enableEvents(SwingEvent.ACTION_EVENT_MASK);
      listenerList.add(ActionListener.class, listener);
    }
  }

  /**
   * Remove action listener.
   *
   * @param listener the action listener
   */
  public void removeActionListener(ActionListener listener) {
    if (listener != null) {
      listenerList.remove(ActionListener.class, listener);
      if (listenerList.getListenerCount(ActionListener.class) == 0) {
        disableEvents(SwingEvent.ACTION_EVENT_MASK);
      }
    }
  }

  /**
   * Returns all action listeners.
   *
   * @return the action listener array
   */
  public ActionListener[] getActionListeners() {
    int count = listenerList.getListenerCount(ActionListener.class);
    ActionListener[] listeners = new ActionListener[count];
    if (count > 0) {
      System.arraycopy(listenerList.getListenersArray(ActionListener.class), 0,
                       listeners,  0, count);
    }
    return listeners;
  }

// Protected Methods. ----------------------------------------------------------

  /**
   * Configures action properties from the specified action.
   *
   * @param action the action
   */
  protected void configurePropertiesFromAction(Action action) {
    if (action != null) {
      setText((String)action.getValue(Action.NAME));
      Integer mnemonic = (Integer)action.getValue(AbstractAction.MNEMONIC_KEY);
      if (mnemonic != null) {
        setMnemonic(mnemonic.intValue());
      }
      setToolTipText((String)action.getValue(Action.SHORT_DESCRIPTION));
      setStatusTipText((String)action.getValue(Action.LONG_DESCRIPTION));
      setWhatsThisText((String)action.getValue(Action.HELP_DESCRIPTION));
      setIcon((Icon)action.getValue(Action.SMALL_ICON));
      setEnabled(action.isEnabled());
    }
  }

  /**
   * Creates and returns PropertyChangeListener for menu item action.
   *
   * @return PropertyChangeListener for menu item action
   */
  protected PropertyChangeListener createActionPropertyChangeListener() {
    return new ActionPropertyChangeListener(this);
  }

  /**
   * Fires action event.
   *
   * @param e the action event
   */
  protected void fireActionPerformed(ActionEvent e) {
    int count = listenerList.getListenerCount(ActionListener.class);
    if (count > 0) {
      ActionListener[] listeners = (ActionListener[])listenerList.
                                       getListenersArray(ActionListener.class);
      for (int i = count; --i >= 0;) {
        listeners[i].actionPerformed(e);
      }
    }
  }

  /**
   * Fires state changed event.
   */
  protected void fireStateChanged() {
    int count = listenerList.getListenerCount(ChangeListener.class);
    if (count > 0) {
      ChangeListener[] listeners = (ChangeListener[])listenerList.
                                       getListenersArray(ChangeListener.class);
      final ChangeEvent changeEvent = this.changeEvent;
      for (int i = count; --i >= 0;) {
        listeners[i].stateChanged(changeEvent);
      }
    }
  }

// Overridden Methods. ---------------------------------------------------------

  /**
   * {@inheritDoc}
   */
  public void setEnabled(boolean enabled) {
    model.setEnabled(enabled);
  }

  /**
   * {@inheritDoc}
   */
  public void updateLocalization() {
    super.updateLocalization();
    if (text != null) {
      _setText(_delegate, localizeString(text));
    }
  }

  /**
   * {@inheritDoc}
   */
  protected String paramString() {
    StringBuffer sb = new StringBuffer(super.paramString());
    if (text != null) {
      sb.append(",text=");
      sb.append(text);
    }
    return sb.toString();
  }

  void componentDestroyed() {
    super.componentDestroyed();
    model.removeChangeListener(changeListener);
  }

  /*!
   * @cute4j.native
   *    overridden="true"
   */
  void _enabledChanged(boolean enabled) {
    final boolean oldEnabled = this.enabled;
    if (oldEnabled != enabled) {
      this.enabled = enabled;
      blockModelEvents = true;
      model.setEnabled(enabled);
      blockModelEvents = false;
      firePropertyChange(PROPERTY_ENABLED, oldEnabled, enabled);
    }
  }

// Package-Private Methods. ----------------------------------------------------

  void processChangeEvent() {
    if (!blockModelEvents) {
      final ButtonModel model = this.model;
      final boolean enabled = model.isEnabled();
      if (this.enabled != enabled) {
        _setEnabled(_delegate, enabled);
      }
      final boolean pressed = model.isPressed();
      if (this.pressed != pressed) {
        _setPressed(_delegate, pressed);
        this.pressed = pressed;
      }
    }
    fireStateChanged();
  }

// Native Callbacks. -----------------------------------------------------------

  boolean _buttonPressed() {
    blockModelEvents = true;
    model.setPressed(true);
    pressed = model.isPressed();
    blockModelEvents = false;
    return pressed;
  }

  boolean _buttonReleased() {
    blockModelEvents = true;
    model.setPressed(false);
    pressed = model.isPressed();
    blockModelEvents = false;
    return !pressed;
  }

  void _buttonClicked(int modifiers) {
    fireActionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
                                        getActionCommand(),
                                        System.currentTimeMillis(), modifiers));
  }

// Package-Private Native Methods. ---------------------------------------------

  native long _create();

  native void _setPressed(long delegate, boolean pressed);

  native void _setText(long delegate, String text);

  native void _setIcon(long delegate, SwingIconSet iconSet);

  native void _setIconSize(long delegate, int width, int height);

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize();

// Inner Classes. --------------------------------------------------------------

  /**
   * Model Change Listener.
   */
  static class ModelListener implements ChangeListener {
    final AbstractButton button;
    public ModelListener(AbstractButton button) {
      this.button = button;
    }
    public void stateChanged(ChangeEvent e) {
      button.processChangeEvent();
    }
  }

  /**
   * Action PropertyChangeListener.
   */
  protected static class ActionPropertyChangeListener implements
                                                       PropertyChangeListener {

    protected AbstractButton button;

    /**
     * Constructs new ActionPropertyChangeListener object.
     *
     * @param button the button object
     */
    public ActionPropertyChangeListener(AbstractButton button) {
      this.button = button;
    }

    /**
     * {@inheritDoc}
     */
    public void propertyChange(PropertyChangeEvent e) {
      Action action = (Action)e.getSource();
      String propertyName = e.getPropertyName();
      if (AbstractAction.PROPERTY_ENABLED.equals(propertyName)) {
        button.setEnabled(action.isEnabled());
      } else if (AbstractAction.NAME.equals(propertyName)) {
        button.setText((String)action.getValue(AbstractAction.NAME));
      } else if (AbstractAction.SMALL_ICON.equals(propertyName)) {
        button.setIcon((Icon)action.getValue(AbstractAction.SMALL_ICON));
      } else if (AbstractAction.SHORT_DESCRIPTION.equals(propertyName)) {
        button.setToolTipText((String)action.getValue(
                                            AbstractAction.SHORT_DESCRIPTION));
      } else if (AbstractAction.MNEMONIC_KEY.equals(propertyName)) {
        Integer mnemonic = (Integer)action.getValue(
                                                  AbstractAction.MNEMONIC_KEY);
        if (mnemonic != null) {
          button.setMnemonic(mnemonic.intValue());
        }
      }
    }

  }

}
